from binary_agent import binary_agent
from packetholder import packetholder
from constants import PCMD, PSIZE, PRIO, MSTATE, MCONSTS, SYSTEM

class memreq(object):
  requestor = None
  req_type = None
  # line not stored since requests array indexed by line

  # these variables for process()
  inv_to_send = None
  excl_payload = False # REP_EXCL should include data as well
  serviced = False

class memctrl(binary_agent):
  start_l = None
  last_l = None
  line_f = None

  directory = None
  dir_states = None

  active_requests = None
  sleeping_requests = None

  def __init__(self, mtable):
    super(memctrl, self).__init__()
    
    for holder in self.in_holders:
      holder.max_packets = MCONSTS.MAX_PACKETS
    for holder in self.out_holders:
      holder.max_packets = MCONSTS.MAX_PACKETS

    self.start_l = mtable.add_entry(self.node_id)
    self.last_l = self.start_l + mtable.sector_size_l - 1
    self.line_f = mtable.line_f

    self.directory = {}
    self.dir_states = {}

    self.active_requests = {}
    self.sleeping_requests = {}

  def count_requests(self):
    return len(self.active_requests) + len(self.sleeping_requests)

  def handle_packet(self, pkt):
    # check to make sure no critical routing failures
    if((pkt.desc < self.start_l) or (self.last_l < pkt.desc)):
      raise NameError("memctrl: Bad Packet DESC")
      return True

    # wakeup sleeping request as needed
    if pkt.desc in self.sleeping_requests:
      self.active_requests[pkt.desc] = self.sleeping_requests.pop(pkt.desc)

    if(pkt.cmd is PCMD.REQ_READ):
      if (pkt.desc in self.sleeping_requests) or (pkt.desc in self.active_requests):
        # do not act further if line is already being acted on by someone else
        return False

      else: #generate a formal request
        # make sure we have space for another request
        if self.count_requests() >= MCONSTS.MAX_REQUESTS: return False

        new_req = memreq()
        new_req.requestor = pkt.src
        new_req.req_type = MSTATE.S

        self.active_requests[pkt.desc] = new_req
        return True

    elif(pkt.cmd is PCMD.REQ_UREAD):
      mstate = self.dir_states.get(pkt.desc, MSTATE.X)

      if (pkt.desc in self.sleeping_requests) or (pkt.desc in self.active_requests):
        # do not act further if line is already being acted on by someone else
        return False

      # check if can handle this request easily
      elif mstate in [MSTATE.X, MSTATE.S]:
        new_pkt = self.issue_packet(PSIZE.REP_UREAD + self.line_f, PRIO.REP_UREAD)
        if new_pkt is None: return False

        new_pkt.dest = pkt.src
        new_pkt.cmd = PCMD.REP_UREAD
        new_pkt.desc = pkt.desc

        new_pkt.age = pkt.age

        return True
        
      else: # all else fails, generate a formal request
        # make sure we have space for another request
        if self.count_requests() >= MCONSTS.MAX_REQUESTS: return False

        new_req = memreq()
        new_req.requestor = pkt.src
        new_req.req_type = MSTATE.X # S counts as well

        self.active_requests[pkt.desc] = new_req
        return True
    
    elif(pkt.cmd is PCMD.REQ_UWRITE):
      mstate = self.dir_states.get(pkt.desc, MSTATE.X)

      if (pkt.desc in self.sleeping_requests) or (pkt.desc in self.active_requests):
        # do not act further if line is already being acted on by someone else
        return False

      # check if can handle this request easily
      elif mstate is MSTATE.X:
        new_pkt = self.issue_packet(PSIZE.REP_UWRITE, PRIO.REP_UWRITE)
        if new_pkt is None: return False

        new_pkt.dest = pkt.src
        new_pkt.cmd = PCMD.REP_UWRITE
        new_pkt.desc = pkt.desc

        new_pkt.age = pkt.age

        return True
        
      else: # all else fails, generate a formal request
        # make sure we have space for another request
        if self.count_requests() >= MCONSTS.MAX_REQUESTS: return False

        new_req = memreq()
        new_req.requestor = pkt.src
        new_req.req_type = MSTATE.I

        self.active_requests[pkt.desc] = new_req
        return True

    elif(pkt.cmd is PCMD.REQ_EXCL):
      if (pkt.desc in self.sleeping_requests) or (pkt.desc in self.active_requests):
        # do not act further if line is already being acted on by someone else
        return False

      else: # generate a formal request for all REQ_EXCL requests
        # make sure we have space for another request
        if self.count_requests() >= MCONSTS.MAX_REQUESTS: return False

        new_req = memreq()
        new_req.requestor = pkt.src
        new_req.req_type = MSTATE.E # S counts as well
        if pkt.extra: new_req.excl_payload = True

        self.active_requests[pkt.desc] = new_req
        return True

    elif(pkt.cmd is PCMD.ACK_INVL):
      mstate = self.dir_states.get(pkt.desc, MSTATE.X)

      # update directory as needed
      dentry = self.directory.get(pkt.desc,[])
      if(pkt.src in dentry): dentry.remove(pkt.src)

      # check if need to transition to X (I states handled by requests)
      if (mstate in [MSTATE.E, MSTATE.S]) and (not dentry):
        self.dir_states.pop(pkt.desc)
        self.directory.pop(pkt.desc)

      return True

    elif(pkt.cmd is PCMD.REQ_DONE):
      # close out the request
      self.active_requests.pop(pkt.desc)

      return True

    else:
      raise NameError("memctrl: Bad Packet CMD")
      return True
  
  def process(self):
    super(memctrl, self).process()

    for req_line, request in self.active_requests.items():
      mstate = self.dir_states.get(req_line, MSTATE.X)
      mdir = self.directory.get(req_line, [])

      # check if all invalidates have come back and can mark line as X
      if mstate is MSTATE.I:
        if not mdir:
          # all entries invalidated!
          self.dir_states[req_line] = MSTATE.X
          self.directory[req_line] = []

        elif request.inv_to_send:
          for dropping in list(request.inv_to_send):
            new_pkt = self.issue_packet(PSIZE.REQ_INVL, PRIO.REQ_INVL)
            if not new_pkt: break
            
            new_pkt.dest = dropping
            new_pkt.cmd = PCMD.REQ_INVL
            new_pkt.desc = req_line

            new_pkt.age = SYSTEM.TIME
            
            request.inv_to_send.remove(dropping)

      elif not request.serviced:
        trigger_invalidate = False

        # handle uncached read request
        if request.req_type is MSTATE.X:
          # check if can just send data
          if (mstate in [MSTATE.X, MSTATE.S]) or (mstate is MSTATE.E and request.requestor in mdir):
            new_pkt = self.issue_packet(PSIZE.REP_UREAD + self.line_f, PRIO.REP_UREAD)
            if new_pkt:
              new_pkt.dest = request.requestor
              new_pkt.cmd = PCMD.REP_UREAD
              new_pkt.desc = req_line

              new_pkt.age = SYSTEM.TIME

              self.active_requests.pop(req_line)

          else: trigger_invalidate = True

        # handle uncached write request
        elif request.req_type is MSTATE.I:
          if(mstate is MSTATE.X):
            new_pkt = self.issue_packet(PSIZE.REP_UWRITE, PRIO.REP_UWRITE)
            if new_pkt:
              new_pkt.dest = request.requestor
              new_pkt.cmd = PCMD.REP_UWRITE
              new_pkt.desc = req_line

              new_pkt.age = SYSTEM.TIME

              self.active_requests.pop(req_line)

          else: trigger_invalidate = True

        # handle cached read request
        elif request.req_type is MSTATE.S:
          if (mstate in [MSTATE.X, MSTATE.S]) or (mstate is MSTATE.E and request.requestor in mdir):
            new_pkt = self.issue_packet(PSIZE.REP_READ + self.line_f, PRIO.REP_READ)
            if new_pkt:
              new_pkt.dest = request.requestor
              new_pkt.cmd = PCMD.REP_READ
              new_pkt.desc = req_line

              new_pkt.age = SYSTEM.TIME

              # add requestee to directory for that line as needed, mark as S
              self.dir_states[req_line] = MSTATE.S
              
              if req_line not in self.directory:
                self.directory[req_line] = []
              dentry = self.directory[req_line]
              if request.requestor not in dentry: dentry.append(request.requestor)

              request.serviced = True

          else: trigger_invalidate = True
        
        # handle cached exclusive read (write) request
        elif request.req_type is MSTATE.E:
          if (mstate is MSTATE.X) or (mstate is MSTATE.E and request.requestor in mdir):
            psize = PSIZE.REP_EXCL
            if request.excl_payload: psize += self.line_f

            new_pkt = self.issue_packet(psize, PRIO.REP_EXCL)
            if new_pkt:
              new_pkt.dest = request.requestor
              new_pkt.cmd = PCMD.REP_EXCL
              new_pkt.desc = req_line

              new_pkt.age = SYSTEM.TIME

              # add requestee to directory, mark as E
              self.dir_states[req_line] = MSTATE.E
              self.directory[req_line] = [request.requestor]

              request.serviced = True

          else: trigger_invalidate = True
        
        if trigger_invalidate:
          request.inv_to_send = [i for i in mdir if i != request.requestor]
          self.dir_states[req_line] = MSTATE.I

          # small trick to temporarily remove requestor from directory
          if request.requestor in mdir: mdir.remove(request.requestor)
          self.directory[req_line] = mdir
